package com.qijian.tool.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Assert;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.qijian.common.exception.ServiceException;
import com.qijian.common.utils.*;
import com.qijian.common.utils.file.AliyunOSSUtil;
import com.qijian.common.utils.file.FileUtils;
import com.qijian.common.utils.file.ZipUtils;
import com.qijian.common.utils.mybatis.WhereEntityTool;
import com.qijian.common.utils.uuid.IdUtils;
import com.qijian.tool.config.FileProperties;
import com.qijian.tool.domain.Storage;
import com.qijian.tool.domain.query.StorageQuery;
import com.qijian.tool.domain.vo.StorageVo;
import com.qijian.tool.mapper.StorageMapper;
import com.qijian.tool.service.IStorageService;
import lombok.RequiredArgsConstructor;
import org.apache.commons.io.IOUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * 本地存储Service业务层处理
 *
 * @author qijian
 * @date 2021-09-06
 */
@Service
@RequiredArgsConstructor
public class StorageServiceImpl extends ServiceImpl<StorageMapper, Storage>
        implements IStorageService {

    private final StorageMapper storageMapper;
    private final FileProperties properties;

    @Override
    public List<Long> filterFids(String classTable, List<Long> fids) {
        Assert.notNull(classTable);
        if (CollectionUtil.isEmpty(fids))
            return new ArrayList<>();
        return this.list(new LambdaQueryWrapper<Storage>()
                .select(Storage::getFid)
                .eq(Storage::getClassTable, classTable)
                .in(Storage::getFid, fids)).stream().map(Storage::getFid).distinct().collect(Collectors.toList());
    }

    @Override
    public List<Storage> listByClassId(Long classId) {
        Assert.notNull(classId);
        return storageMapper.selectList(new LambdaQueryWrapper<Storage>()
                .eq(Storage::getClassId, classId));
    }

    @Override
    public List<Storage> listByIds(Set<Long> ids) {
        Assert.notNull(ids);
        return storageMapper.selectList(new LambdaQueryWrapper<Storage>()
                .in(Storage::getStorageId, ids));
    }


    @Override
    public List<Storage> listByClassId(Long classId, String classTable) {
        Assert.notNull(classId);
        Assert.notNull(classTable);
        return storageMapper.selectList(new LambdaQueryWrapper<Storage>()
                .eq(Storage::getClassId, classId)
                .eq(Storage::getClassTable, classTable));
    }

    @Override
    public Map<Long, List<Storage>> mapByClassIds(Collection<Long> classIds) {
        Assert.notEmpty(classIds);
        return storageMapper.selectList(new LambdaQueryWrapper<Storage>()
                        .in(Storage::getClassId, classIds))
                .stream().collect(Collectors.groupingBy(Storage::getClassId));
    }

    @Override
    public Map<Long, List<Storage>> mapByClassIds(Collection<Long> classIds, String classTable) {
        Assert.notEmpty(classIds);
        Assert.notNull(classTable);
        return storageMapper.selectList(new LambdaQueryWrapper<Storage>()
                        .in(Storage::getClassId, classIds)
                        .eq(Storage::getClassTable, classTable))
                .stream().collect(Collectors.groupingBy(Storage::getClassId));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean importCadZip(MultipartFile file) {
        //获得原始文件名;
        String fileRealName = file.getOriginalFilename();
        //点号的位置
        int pointIndex = fileRealName.lastIndexOf(".");
        //截取文件名
        String filePre = fileRealName.substring(0, pointIndex);
        //截取文件后缀
        String fileSuffix = fileRealName.substring(pointIndex);

        String tempPath = System.getProperty("java.io.tmpdir") + File.separator + filePre + "_" + System.currentTimeMillis();
        // 判断文件路径下的文件夹是否存在，不存在则创建
        File zipFile = new File(tempPath);
        if (!zipFile.exists()) {
            zipFile.mkdirs();
        }
        File imageFile = new File(zipFile, "image");
        if (!imageFile.exists()) {
            imageFile.mkdirs();
        }
        try {
            File f = File.createTempFile(fileRealName, fileSuffix);
            file.transferTo(f);
            ZipUtils.unzip(f, tempPath);
            //遍历path下的文件和目录，放在File数组中
            File[] fs = zipFile.listFiles();
            List<Storage> storages = new ArrayList<>();
            //遍历File[]数组
            for (File i : fs) {
                if (!i.isDirectory()) {
                    String suffix = i.getName().substring(i.getName().lastIndexOf("."));
                    if (suffix.equalsIgnoreCase(".dwg")) {
                        String imageName = i.getName().substring(0, i.getName().lastIndexOf(".")) + "_PNG.png";
                        CADFileUtil.CADFileToImage(i.getAbsolutePath(), imageFile.getAbsolutePath(), imageName);
                        String fileDwgPath = AliyunOSSUtil.uploadObject2OSS(AliyunOSSUtil.getOSSClient(), i,
                                "CAD" + File.separator + filePre, i.getName());
                        storages.add(createStorage(i.getName(), fileDwgPath, FileUtils.getSize(i.length())));
                        File pngFile = new File(imageFile.getAbsolutePath() + File.separator + imageName);
                        String filePngPath = AliyunOSSUtil.uploadObject2OSS(AliyunOSSUtil.getOSSClient(), pngFile,
                                "CAD" + File.separator + filePre + File.separator + "image", imageName);
                        storages.add(createStorage(imageName, filePngPath, FileUtils.getSize(pngFile.length())));
                    }
                }
            }
            if (storages.size() > 0) {
                this.saveOrUpdateBatch(storages);
            }
            org.apache.commons.io.FileUtils.delete(f);
            org.apache.commons.io.FileUtils.deleteDirectory(zipFile);
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    @Override
    public void zip(HttpServletResponse response, List<String> urls) {
        String fileName = SecurityUtils.getNickName();
        // 默认文件名以时间戳作为前缀
        if (StringUtils.isBlank(fileName)) {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
            fileName = sdf.format(new Date());
        }
        String downloadName = fileName + ".zip";

        String sourcePath = System.getProperty("java.io.tmpdir") + File.separator + "zipFile";
        urls.forEach(url -> {
            AliyunOSSUtil.fileDownload(sourcePath, url);
        });
        String zipName = System.getProperty("java.io.tmpdir") + File.separator + SecurityUtils.getNickName();
        try {
            // 将文件进行打包下载
            try {
                OutputStream os = response.getOutputStream();
                // 接收压缩包字节
                byte[] data = createZip(sourcePath);
                response.reset();
                response.setCharacterEncoding("UTF-8");
                response.addHeader("Access-Control-Allow-Origin", "*");
                response.setHeader("Access-Control-Expose-Headers", "*");
                // 下载文件名乱码问题
                response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(downloadName, "UTF-8"));
                //response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + downloadName);
                response.addHeader("Content-Length", "" + data.length);
                response.setContentType("application/octet-stream;charset=UTF-8");
                IOUtils.write(data, os);
                os.flush();
                os.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
//
//            org.apache.commons.io.FileUtils.delete(new File(zipName));
            org.apache.commons.io.FileUtils.deleteDirectory(new File(sourcePath));
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    @Override
    public void zipWithSubFolder(HttpServletResponse response, Map<String, List<String>> folderMap) {
        String fileName = SecurityUtils.getNickName();
        // 默认文件名以时间戳作为前缀
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
        fileName += "_" + sdf.format(new Date());

        String downloadName = fileName + ".zip";

        String sourcePath = System.getProperty("java.io.tmpdir") + File.separator + "zipFile" + File.separator + fileName;
        folderMap.forEach((folder, urls) -> {
            urls.forEach(url -> {
                AliyunOSSUtil.fileDownloadWithFolder(sourcePath, folder, url);
            });
        });

        try {
            // 将文件进行打包下载
            try {
                OutputStream os = response.getOutputStream();
                // 接收压缩包字节
                byte[] data = createZip(sourcePath);
                response.reset();
                response.setCharacterEncoding("UTF-8");
                response.addHeader("Access-Control-Allow-Origin", "*");
                response.setHeader("Access-Control-Expose-Headers", "*");
                // 下载文件名乱码问题
                response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(downloadName, "UTF-8"));
                //response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + downloadName);
                response.addHeader("Content-Length", "" + data.length);
                response.setContentType("application/octet-stream;charset=UTF-8");
                IOUtils.write(data, os);
                os.flush();
                os.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
//
//            org.apache.commons.io.FileUtils.delete(new File(zipName));
            org.apache.commons.io.FileUtils.deleteDirectory(new File(sourcePath));
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    @Override
    public void zipByProjectIds(HttpServletResponse response, List<Long> projectIds) {

        List<StorageVo> results = storageMapper.selectByProjectIds(projectIds);
        if (results != null && results.size() > 0) ;
        {
            Map<String, List<String>> folderListMap = generateMap4Inspection(results);

            zipWithSubFolder(response, folderListMap);
        }
    }

    public static Map<String, List<String>> generateMap4Inspection(List<StorageVo> storageList) {
        return storageList.stream()
                .collect(Collectors.groupingBy(StorageVo::getCreateBy, Collectors.mapping(StorageVo::getUrl, Collectors.toList())));

    }

    @Override
    public void zip(HttpServletResponse response, List<String> urls, String fileName) {
        // 默认文件名以时间戳作为前缀
        if (StringUtils.isBlank(fileName)) {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
            fileName = sdf.format(new Date());
        }
        String downloadName = fileName + ".zip";

        String sourcePath = System.getProperty("java.io.tmpdir") + File.separator + "zipFile";
        urls.forEach(url -> {
            AliyunOSSUtil.fileDownload(sourcePath, url);
        });
        String zipName = System.getProperty("java.io.tmpdir") + File.separator + SecurityUtils.getNickName();
        try {
            // 将文件进行打包下载
            try {
                OutputStream os = response.getOutputStream();
                // 接收压缩包字节
                byte[] data = createZip(sourcePath);
                response.reset();
                response.setCharacterEncoding("UTF-8");
                response.addHeader("Access-Control-Allow-Origin", "*");
                response.setHeader("Access-Control-Expose-Headers", "*");
                // 下载文件名乱码问题
                response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(downloadName, "UTF-8"));
                //response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + downloadName);
                response.addHeader("Content-Length", "" + data.length);
                response.setContentType("application/octet-stream;charset=UTF-8");
                IOUtils.write(data, os);
                os.flush();
                os.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
//
//            org.apache.commons.io.FileUtils.delete(new File(zipName));
            org.apache.commons.io.FileUtils.deleteDirectory(new File(sourcePath));
        } catch (IOException e) {
            e.printStackTrace();
        }

    }


    @Override
    public void initFileClass(Long classId, String classType, List<Storage> files) {
        // 附件
        if (CollectionUtil.isNotEmpty(files)) {
            List<Storage> updates = new ArrayList<>();
            files.forEach(action -> {
                if (action.getStorageId() != null) {
                    Storage storage = new Storage();
                    storage.setStorageId(action.getStorageId());
                    storage.setClassTable(classType);
                    if (action.getClassify() != null) {
                        storage.setClassify(action.getClassify());
                    }
                    storage.setClassId(classId);
                    updates.add(storage);
                }
            });
            if (CollectionUtil.isNotEmpty(updates)) {
                this.updateBatchById(updates);
            }
        }
    }

    @Override
    public void copyFileClass(Long classId, String classType, List<Long> storageIds) {
        copyFileClass(classId, classType, null, storageIds);
    }

    @Override
    public void copyFileClass(Long classId, String classType, Integer classify, List<Long> storageIds) {
        if (CollectionUtil.isEmpty(storageIds))
            return;
        List<Storage> storages = this.listByIds(storageIds);
        // 附件
        if (CollectionUtil.isNotEmpty(storages)) {
            List<Storage> insertList = new ArrayList<>();
            storages.forEach(storage -> {
                storage.setStorageId(null);
                storage.setClassTable(classType);
                storage.setClassId(classId);
                storage.setClassify(classify);
                insertList.add(storage);
            });

            if (CollectionUtil.isNotEmpty(insertList)) {
                this.saveBatch(insertList);
            }
        }
    }

    @Override
    public Boolean remove(List<Long> ids, List<Long> excludeIds) {
        StorageQuery storageQuery = new StorageQuery();
        storageQuery.setClassIds(ids);
        if (CollectionUtil.isNotEmpty(excludeIds)) {
            storageQuery.setStorageIdsNot(excludeIds);
        }
        return this.remove(WhereEntityTool.invoke(storageQuery));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Storage importFileByByte(byte[] bytes, String folder, Storage storage) {
        FileUtils.checkSize(properties.getMaxSize(), bytes.length);
        // 文件名
        String suffix = FileUtils.getFileExtendName(bytes);
        String fileName = IdUtils.fastUUID() + "." + suffix;
        folder = folder + "/" + FileUtils.getFolder(suffix) + "/" + DateUtils.datePath();
        String filePath = AliyunOSSUtil.uploadObject2OSS(AliyunOSSUtil.getOSSClient(), new ByteArrayInputStream(bytes), folder, fileName);
        storage.setStorageType(Storage.StorageType.ALIOSS);
        storage.setType(FileUtils.getFileType(suffix));
        storage.setRealName(fileName);
        storage.setSuffix(suffix);
        storage.setUrl(filePath);
        storage.setSize(FileUtils.getSize(bytes.length));
        storage.setDescription(null);
        save(storage);
        return storage;
    }

    @Override
    public List<Long> storageIdToFilter(String classTable, Collection<Long> classIds) {
        assert StringUtils.isNotBlank(classTable);
        //如果限制的实体类ids不为null，但是ids给了个空,理解为需要限制，所以直接返回空
        if (classIds != null && classIds.size() == 0)
            return Collections.emptyList();
        QueryWrapper<Storage> wrapper = new QueryWrapper<>();
        wrapper.select("storage_id");
        wrapper.eq("class_table", classTable);
        if (classIds != null)
            wrapper.in("class_id", classIds);
        return storageMapper.selectList(wrapper).stream().map(Storage::getStorageId).collect(Collectors.toList());
    }


    private Storage createStorage(String name, String path, String size) {
        String type = FileUtils.getFileType(name.substring(name.lastIndexOf(".") + 1));
        Storage storage = new Storage();
        storage.setClassTable("NC");
        storage.setName(FileUtils.getFileNameNoEx(name));
        storage.setStorageType(Storage.StorageType.ALIOSS);
        storage.setRealName(name);
        storage.setSuffix(name.substring(name.lastIndexOf(".") + 1));
        storage.setUrl(path);
        storage.setType(type);
        storage.setSize(size);
        StorageQuery query = new StorageQuery();
        query.setClassTable(storage.getClassTable());
        query.setName(storage.getName());
        query.setSuffix(storage.getSuffix());
        Storage old = this.getOne(WhereEntityTool.invoke(query));
        if (old != null)
            storage.setStorageId(old.getStorageId());
        return storage;
    }

    private static byte[] createZip(String sourceFilePath) throws Exception {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ZipOutputStream zip = new ZipOutputStream(outputStream);
        // 将目标文件打包成zip导出
        File file = new File(sourceFilePath);
        handlerFile(zip, file, "");
        // 无异常关闭流，它将无条件的关闭一个可被关闭的对象而不抛出任何异常。
        IOUtils.closeQuietly(zip);
        return outputStream.toByteArray();
    }

    private static void handlerFile(ZipOutputStream zip, File file, String dir) throws Exception {
        // 如果当前的是文件夹，则循环里面的内容继续处理
        if (file.isDirectory()) {
            //得到文件列表信息
            File[] fileArray = file.listFiles();
            if (fileArray == null) {
                return;
            }
            //将文件夹添加到下一级打包目录
            zip.putNextEntry(new ZipEntry(dir + "/"));
            dir = dir.length() == 0 ? "" : dir + "/";
            // 递归将文件夹中的文件打包
            for (File f : fileArray) {
                handlerFile(zip, f, dir + f.getName());
            }
        } else {
            // 如果当前的是文件，打包处理
            BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
            ZipEntry entry = new ZipEntry(dir);
            zip.putNextEntry(entry);
            zip.write(org.apache.commons.io.FileUtils.readFileToByteArray(file));
            IOUtils.closeQuietly(bis);
            zip.flush();
            zip.closeEntry();
        }
    }

    @Override
    public Storage addStoreData(String classTable, Long classId, Integer classify, String description, MultipartFile file, String folder, String suffix) {
        FileUtils.checkSize(properties.getMaxSize(), file.getSize());
        String type = FileUtils.getFileType(suffix);
        String fileName, filePath;
        // 文件名
        String realName = FileUtils.getFileNameNoEx(file.getOriginalFilename());
//        assert realName != null;
//        //文件名去除非数字、字母、汉字的所有字符
//        realName = realName.replaceAll("[^a-zA-Z0-9_\\-\\u4e00-\\u9fa5]+","");
        String nowStr = "-" + FileUtils.FORMAT_DATE.format(new Date());
        fileName = realName + nowStr + "." + suffix;
        filePath = AliyunOSSUtil.uploadObject2OSS(AliyunOSSUtil.getOSSClient(), file, folder, fileName);

        Storage storage = new Storage();
        storage.setClassTable(classTable);
        storage.setClassId(classId);
        storage.setClassify(classify);
        storage.setName(FileUtils.getFileNameNoEx(file.getOriginalFilename()));
//        storage.setName(file.getOriginalFilename());
        storage.setStorageType(Storage.StorageType.ALIOSS);
        storage.setRealName(fileName);
        storage.setSuffix(suffix);
        storage.setUrl(filePath);
        storage.setType(type);
        storage.setSize(FileUtils.getSize(file.getSize()));
        storage.setDescription(description);
        this.save(storage);
        return storage;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean removeAndFileByIds(List<Long> storageIds) {
        for (Long id : storageIds) {
            Storage storage = this.getById(id);
            if (storage == null) {
                continue;
            }
            long count = this.count(new LambdaQueryWrapper<Storage>().eq(Storage::getUrl, storage.getUrl()));
            if (count == 1 && Storage.StorageType.ALIOSS == storage.getStorageType() && storage.getUrl().contains("http")) {
                AliyunOSSUtil.deleteFile(AliyunOSSUtil.getOSSClient(), storage.getUrl());
            }
        }
        this.removeByIds(storageIds);
        return true;
    }

    /**
     * 删除数据存储带模块
     *
     * @param storageIds
     * @param module
     * @return
     */
    @Override
    public boolean removeAndFileWithModule(List<Long> storageIds, String module) {

        if (ObjectUtils.isEmpty(module)) {
            return removeAndFileByIds(storageIds);
        }
        for (Long id : storageIds) {
            Storage storage = this.getById(id);
            if (storage == null) {
                continue;
            }

            // 销售报价的附件删除只能本人
            if (ObjectUtils.equals(module, "saleContract") && ObjectUtils.notEqual(storage.getCreateBy(), SecurityUtils.getNickName())) {
                throw new ServiceException(StringUtils.format("{}非本人上传，无法删除", storage.getName()));
            }
            long count = this.count(new LambdaQueryWrapper<Storage>().eq(Storage::getUrl, storage.getUrl()));
            if (count == 1 && Storage.StorageType.ALIOSS == storage.getStorageType() && storage.getUrl().contains("http")) {
                AliyunOSSUtil.deleteFile(AliyunOSSUtil.getOSSClient(), storage.getUrl());
            }
        }
        this.removeByIds(storageIds);
        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean removeAndFileByClassIds(List<Long> classIds) {
        StorageQuery storageQuery = new StorageQuery();
        storageQuery.setClassIds(classIds);
        List<Storage> storageList = this.list(WhereEntityTool.invoke(storageQuery));
        if (CollUtil.isNotEmpty(storageList)) {
            for (Storage storage : storageList) {
                if (storage == null) {
                    continue;
                }
                long count = this.count(new LambdaQueryWrapper<Storage>().eq(Storage::getUrl, storage.getUrl()));
                if (count == 1 && Storage.StorageType.ALIOSS == storage.getStorageType() && storage.getUrl().contains("http")) {
                    AliyunOSSUtil.deleteFile(AliyunOSSUtil.getOSSClient(), storage.getUrl());
                }
            }
            this.removeByIds(storageList);
        }
        return true;
    }


    @Override
    public Storage addStoreDataWithWatermark(String classTable, Long classId, Integer classify, String description, MultipartFile file, String folder, String suffix) {
        FileUtils.checkSize(properties.getMaxSize(), file.getSize());
        String type = FileUtils.getFileType(suffix);
        String fileName, filePath;
        // 文件名
        String realName = FileUtils.getFileNameNoEx(file.getOriginalFilename());
//        assert realName != null;
//        //文件名去除非数字、字母、汉字的所有字符
//        realName = realName.replaceAll("[^a-zA-Z0-9_\\-\\u4e00-\\u9fa5]+","");
        String nowStr = "-" + FileUtils.FORMAT_DATE.format(new Date());
        fileName = realName + nowStr + "." + suffix;
        filePath = AliyunOSSUtil.uploadObject2OSSWithWaterMark(AliyunOSSUtil.getOSSClient(), file, folder, fileName);

        Storage storage = new Storage();
        storage.setClassTable(classTable);
        storage.setClassId(classId);
        storage.setClassify(classify);
        storage.setName(FileUtils.getFileNameNoEx(file.getOriginalFilename()));
//        storage.setName(file.getOriginalFilename());
        storage.setStorageType(Storage.StorageType.ALIOSS);
        storage.setRealName(fileName);
        storage.setSuffix(suffix);
        storage.setUrl(filePath);
        storage.setType(type);
        storage.setSize(FileUtils.getSize(file.getSize()));
        storage.setDescription(description);
        this.save(storage);
        return storage;
    }
}
